/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 d3leaf@126.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.mapperdb.builder.annotation;

import java.lang.reflect.Method;

import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.scripting.xmltags.DynamicSqlSource;
import org.apache.ibatis.scripting.xmltags.SqlNode;

import com.mapperdb.db.ResultEntity;
import com.mapperdb.joor.Reflect;
import com.mapperdb.provider.DbProvider;
import com.mapperdb.util.MapperUtils;

/**
 * 自定义 ProviderSqlSource
 * @author Administrator
 *
 */
public class ProviderSqlSourceSupper implements SqlSource {
	MappedStatement mappedStatement;
	Class<?> providerType;
	Method providerMethod;
	String providerMethodName;
	ResultEntity resultEntity;

	public ProviderSqlSourceSupper(MappedStatement mappedStatement, ResultEntity resultEntity, Class<?> providerType,
			String providerMethodName) {
		this.mappedStatement = mappedStatement;
		this.resultEntity = resultEntity;
		this.providerType = providerType;
		this.providerMethodName = providerMethodName;
		this.init(providerType, providerMethodName);
	}

	protected void init(Class<?> providerType, String providerMethodName) {
		this.providerMethod = getProviderMethod(providerType, providerMethodName);
	}

	protected Method getProviderMethod(Class<?> providerType, String providerMethodName) {
		Method providerMethod = null;
		for (Method m : providerType.getMethods()) {
			if (providerMethodName.equals(m.getName())) {
				Class<?> returnType = m.getReturnType();
				if (m.getParameterTypes().length < 2 //
						&& (returnType == Void.TYPE || returnType == String.class || returnType == SqlNode.class || returnType == SqlSource.class)) {
					providerMethod = m;
					break;
				}
			}
		}

		if (providerMethod == null) { //
			throw new BuilderException("Error creating SqlSource for SqlProvider. Method '" + providerMethodName
					+ "' not found in SqlProvider '" + providerType.getName() + "'.");
		}
		return providerMethod;
	}

	public BoundSql getBoundSql(Object parameterObject) {
		SqlSource sqlSource = createSqlSource(parameterObject);
		return sqlSource.getBoundSql(parameterObject);
	}

	protected SqlSource createSqlSource(Object parameterObject) {
		DbProvider provider = null;
		try {
			SqlSource sqlSource = null;
			if (DbProvider.class.isAssignableFrom(providerType)) {
				provider = Reflect.on(providerType).create().<DbProvider> get();
				provider.init(mappedStatement, resultEntity);

				// 参数
				Object arg = getParam(providerMethod, parameterObject);

				Class<?> returnType = providerMethod.getReturnType();

				if (Void.TYPE.isAssignableFrom(returnType)) {
					invoke(providerMethodName, provider, arg);
				} else if (SqlNode.class.isAssignableFrom(returnType)) {
					SqlNode sqlNode = invoke(providerMethodName, provider, arg);
					sqlSource = new DynamicSqlSource(mappedStatement.getConfiguration(), sqlNode);
				} else if (String.class.equals(returnType)) {
					String xmlSql = invoke(providerMethodName, provider, arg);
					sqlSource = MapperUtils.createSqlSource(mappedStatement, xmlSql);
				} else if (SqlSource.class.equals(returnType)) {
					sqlSource = invoke(providerMethodName, provider, arg);
				} else {
					throw new BuilderException("自定义Mapper方法返回类型错误,可选的返回类型为 void,SqlNode,String,SqlSource 四种!");
				}
				// TODO BUG 不能重复查询
				if (sqlSource != null) MapperUtils.setSqlSource(mappedStatement, sqlSource);
				return sqlSource;
			} else {
				throw new BuilderException("没有实现 Provider 接口:" + providerType.getName());
			}
		} catch (Exception e) {
			if (provider != null) provider.destroy();
			throw new BuilderException("Error creating SqlSource for SqlProvider.  Cause: " + e, e);
		}
	}

	public Class<?> getProviderType() {
		return providerType;
	}

	public MappedStatement getMappedStatement() {
		return mappedStatement;
	}

	public String getProviderMethodName() {
		return providerMethodName;
	}

	public ResultEntity getResultEntity() {
		return resultEntity;
	}

	// 获得  Param
	protected Object getParam(Method providerMethod, Object parameterObject) {
		Object args = null;// paramLength == 0
		if (providerMethod.getParameterTypes().length == 1) {
			args = new Object[] { parameterObject };
		}
		return args;
	}

	protected <T> T invoke(String name, Object obj, Object args) {
		Reflect call = Reflect.on(obj).call(name, (Object[]) args);
		return call.get();
	}
}
